package com.improve.frame.log.aspect;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.improve.frame.auth.utils.SecurityUtil;
import com.improve.frame.common.bean.ResponseData;
import com.improve.frame.common.enums.BusinessStatus;
import com.improve.frame.common.utils.WebUtil;
import com.improve.frame.log.annotation.OperationLog;
import com.improve.frame.log.domain.SysLog;
import com.improve.frame.log.service.LogService;
import eu.bitwalker.useragentutils.Browser;
import eu.bitwalker.useragentutils.UserAgent;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;

/**
 * @Description
 *      系统请求日志切面
 * @Author wzxing918
 * @Date 2021-03-31
 * @Version 1.0.0
 **/
@Slf4j
@Aspect
@ConditionalOnClass({HttpServletRequest.class, RequestContextHolder.class})
public class SysLogAspect {
    @Resource
    private LogService logService;

    @Pointcut("@annotation(com.improve.frame.log.annotation.OperationLog)")
    public void operationLogPointcut() {
        // 操作日志切点
    }

    /**
     * 操作日志环绕通知
     * @param joinPoint 切入点
     * @return
     * @throws Throwable
     */
    @Around("operationLogPointcut()")
    public Object operationLog(ProceedingJoinPoint joinPoint) throws Throwable {
        Object result;
        SysLog sysLog = new SysLog();
        try {
            long startTime = System.currentTimeMillis();
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();

            sysLog.setRequestUrl(request.getRequestURL().toString());
            sysLog.setRequestMethod(request.getMethod());

            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            sysLog.setMethodName(joinPoint.getSignature().getDeclaringTypeName() + "." + signature.getName() + "()");

            OperationLog operationLog = method.getAnnotation(OperationLog.class);
            List<String> encryptFields = CollUtil.toList(operationLog.encryptFields());
            // 请求的方法参数
            Object[] args = joinPoint.getArgs();
            LocalVariableTableParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
            String[] paramNames = parameterNameDiscoverer.getParameterNames(method);
            if (args != null && paramNames != null) {
                StringBuilder params = new StringBuilder();
                for (int i = 0; i < args.length; i++) {
                    String paramName = paramNames[i];
                    if (encryptFields.contains(paramName)) {
                        params.append("  ").append(paramName).append(": ").append(operationLog.encryptKey());
                    } else {
                        params.append("  ").append(paramName).append(": ").append(args[i]);
                    }
                }
                sysLog.setRequestParam(params.toString());
            }
            sysLog.setModuleName(operationLog.module());
            if (ObjectUtil.isNotNull(SecurityUtil.getLoginUser())) {
                sysLog.setOperatorCode(SecurityUtil.getLoginUser().getUserId().toString());
                sysLog.setOperatorName(SecurityUtil.getUsername());
            }
            sysLog.setOperationIp(WebUtil.getRequestIp());

            String ua = request.getHeader("User-Agent");
            UserAgent userAgent = UserAgent.parseUserAgentString(ua);
            sysLog.setOperationClient(userAgent.getOperatingSystem().getName());
            Browser browser = userAgent.getBrowser();
            if (ObjectUtil.isNotNull(browser)) {
                sysLog.setOperationBrowser(browser.getName());
            }
            if (ObjectUtil.isNotNull(userAgent.getBrowserVersion())) {
                sysLog.setBrowserVersion(userAgent.getBrowserVersion().getVersion());
            }
            sysLog.setOperationType(operationLog.type());
            sysLog.setOperationDesc(operationLog.desc());

            result = joinPoint.proceed();
            if (result instanceof ResponseData) {
                ResponseData<?> data = (ResponseData) result;
                if (Boolean.TRUE.equals(data.isSuccess())) {
                    sysLog.setBusinessStatus(BusinessStatus.SUCCESS);
                    sysLog.setOperationResult(BusinessStatus.SUCCESS.getDesc());
                } else {
                    sysLog.setBusinessStatus(BusinessStatus.FAILURE);
                    sysLog.setOperationResult(BusinessStatus.FAILURE.getDesc());
                }
            }
            sysLog.setResponseResult(JSON.toJSONString(result));
            sysLog.setOperationTime(System.currentTimeMillis() - startTime);
        } catch (Throwable e) {
            log.error("操作异常", e);
            sysLog.setBusinessStatus(BusinessStatus.ERROR);
            sysLog.setErrorMsg(ExceptionUtil.stacktraceToString(e));
            throw e;
        } finally {
            //异步插入日志
            CompletableFuture.runAsync(() -> logService.save(sysLog));
        }
        return result;
    }
}
